/**
* @file dvpp_manager.cpp
*
* Copyright (C) Huawei Technologies Co., Ltd. 2019-2020. All Rights Reserved.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "dvpp_manager.h"
#include "common/log_inner.h"
#include "aicpu/common/aicpu_task_struct.h"
#include "single_op/dvpp/v100/image_processor_v100.h"
#include "single_op/dvpp/v200/image_processor_v200.h"
#if (OS_TYPE != WIN)
#include "single_op/dvpp/v300/image_processor_v300.h"
#endif
#include "single_op/dvpp/v100/video_processor_v100.h"
#include "single_op/dvpp/v200/video_processor_v200.h"
#include "toolchain/profiling_manager.h"

namespace acl {
    namespace dvpp {
        DvppManager& DvppManager::GetInstance()
        {
            static DvppManager instance;
            // check and init dvpp version
            instance.CheckRunModeAndDvppVersion();
            return instance;
        }

        ImageProcessor *DvppManager::GetImageProcessor() const
        {
            return imageProcessor_.get();
        }

        VideoProcessor *DvppManager::GetVideoProcessor() const
        {
            return videoProcessor_.get();
        }

        DvppVersion DvppManager::GetDvppVersion() const
        {
            return version_;
        }

        uint32_t DvppManager::GetAicpuVersion() const
        {
            return aicpuVersion_;
        }

        void DvppManager::CheckRunModeAndDvppVersion()
        {
            if ((version_ != DVPP_KERNELS_UNKOWN) &&
                (imageProcessor_ != nullptr) &&
                (videoProcessor_ != nullptr)) {
                return;
            }

            const std::lock_guard<std::mutex> dvppVersionLock(dvppVersionMutex_);
            if (version_ != DVPP_KERNELS_UNKOWN) {
                ACL_LOG_INFO("current dvpp version is %d.", version_);
                return;
            }

            // get acl run mode
            const aclError getRunModeRet = aclrtGetRunMode(&aclRunMode_);
            if (getRunModeRet != ACL_SUCCESS) {
                ACL_LOG_INNER_ERROR("[Get][RunMode]get run mode failed, errorCode = %d.", getRunModeRet);
                return;
            }

            // get dvpp kernel version
            const aclError getVersionRet = GetDvppKernelVersion();
            if (getVersionRet != ACL_SUCCESS) {
                ACL_LOG_INNER_ERROR("[Get][Version]get dvpp kernel version failed, errorCode = %d", getVersionRet);
                return;
            }

            // init dvpp processor
            const aclError initProcessorRet = InitDvppProcessor();
            if (initProcessorRet != ACL_SUCCESS) {
                ACL_LOG_INNER_ERROR("[Init][Processor]init dvpp processor failed, errorCode = %d.", initProcessorRet);
                version_ = DVPP_KERNELS_UNKOWN;
                return;
            }
        }

        aclError DvppManager::GetDvppKernelVersion()
        {
            // create stream
            rtStream_t rtStream = nullptr;
            const rtError_t rtCreate = rtStreamCreate(&rtStream, static_cast<int32_t>(RT_STREAM_PRIORITY_DEFAULT));
            if (rtCreate != RT_ERROR_NONE) {
                ACL_LOG_CALL_ERROR("[Create][Stream]create stream failed, runtime errorCode = %d.", rtCreate);
                return ACL_GET_ERRCODE_RTS(rtCreate);
            }

            // malloc device memory
            constexpr uint32_t size = static_cast<uint32_t>(sizeof(uint32_t));
            void *dvppVersionAddr = nullptr;
            rtError_t rtErr = rtMalloc(&dvppVersionAddr, size,
                RT_MEMORY_DEFAULT | RT_MEMORY_POLICY_DEFAULT_PAGE_ONLY, acl::ACL_MODE_ID_U16);
            if (rtErr != RT_ERROR_NONE) {
                (void) rtStreamDestroy(rtStream);
                ACL_LOG_CALL_ERROR("[Malloc][Mem]alloc device memory failed, runtime errorCode = %d", rtErr);
                return ACL_GET_ERRCODE_RTS(rtErr);
            }

            // get version have 1 output and 1 input
            constexpr uint32_t ioAddrNum = 1U;
            constexpr size_t argsSize = sizeof(aicpu::AicpuParamHead) +
                (ioAddrNum * sizeof(uint64_t));
            char_t args[argsSize] = {};
            const auto paramHead = reinterpret_cast<aicpu::AicpuParamHead *>(args);
            paramHead->length = static_cast<uint32_t>(argsSize);
            paramHead->ioAddrNum = ioAddrNum;
            const auto ioAddr = reinterpret_cast<uint64_t *>(args + sizeof(aicpu::AicpuParamHead));
            ioAddr[0] = reinterpret_cast<uintptr_t>(dvppVersionAddr);

            // launch get version task
            rtErr = rtCpuKernelLaunch(acl::dvpp::DVPP_KERNELS_SONAME,
                                      acl::dvpp::DVPP_KERNELNAME_GET_VERSION,
                                      1U, // blockDim default 1
                                      args,
                                      static_cast<uint32_t>(argsSize),
                                      nullptr, // no need smDesc
                                      rtStream);
            if (rtErr != RT_ERROR_NONE) {
                (void) rtFree(dvppVersionAddr);
                dvppVersionAddr = nullptr;
                (void) rtStreamDestroy(rtStream);
                ACL_LOG_CALL_ERROR("[Get][Version]dvpp get version call rtCpuKernelLaunch failed, "
                    "runtime errorCode = %d.", rtErr);
                return ACL_GET_ERRCODE_RTS(rtErr);
            }

            // async memcpy
            uint32_t version = DVPP_KERNELS_V100;
            if (aclRunMode_ == ACL_HOST) {
                const auto rtRet = rtMemcpyAsync(&version, size,
                                                 static_cast<const void *>(dvppVersionAddr), size,
                                                 RT_MEMCPY_DEVICE_TO_HOST,
                                                 rtStream);
                if (rtRet != RT_ERROR_NONE) {
                    (void) rtFree(dvppVersionAddr);
                    dvppVersionAddr = nullptr;
                    (void) rtStreamDestroy(rtStream);
                    ACL_LOG_CALL_ERROR("[Copy][Mem]memcpy to host memory failed, size = %u, "
                        "runtime errorCode = %d.", size, rtRet);
                    return ACL_GET_ERRCODE_RTS(rtRet);
                }
            }

            // stream synchronize
            rtErr = rtStreamSynchronize(rtStream);
            if (rtErr != RT_ERROR_NONE) {
                (void) rtFree(dvppVersionAddr);
                dvppVersionAddr = nullptr;
                (void) rtStreamDestroy(rtStream);
                ACL_LOG_CALL_ERROR("[Sync][Stream]synchronize stream failed, runtime errorCode = %d", rtErr);
                return ACL_GET_ERRCODE_RTS(rtErr);
            }

            // get dvpp version
            if (aclRunMode_ == ACL_DEVICE) {
                version = *(static_cast<uint32_t *>(dvppVersionAddr));
            }
            const std::string versionStr = std::to_string(version);
            // the length of old dvpp version is 3
            // the length of new dvpp version is 6
            if (versionStr.length() == 3U) {
                version_ = static_cast<DvppVersion>(version);
                aicpuVersion_ = 0U;
            } else if (versionStr.length() == 6U) {
                version_ = static_cast<DvppVersion>(version / 1000U);
                aicpuVersion_ = version % 1000U;
            } else {
                (void) rtFree(dvppVersionAddr);
                dvppVersionAddr = nullptr;
                (void) rtStreamDestroy(rtStream);
                ACL_LOG_INNER_ERROR("[Unknown][Version]unknown dvpp version length = %zu, "
                    "dvpp version length must be 3 or 6", versionStr.length());
                return ACL_ERROR_FEATURE_UNSUPPORTED;
            }
            ACL_LOG_INFO("get version success, dvpp version = %d, aicpu version = %u",
                static_cast<int32_t>(version_), aicpuVersion_);

            // destroy resource: device memory and stream
            rtErr = rtFree(dvppVersionAddr);
            dvppVersionAddr = nullptr;
            if (rtErr != RT_ERROR_NONE) {
                ACL_LOG_CALL_ERROR("[Free][Mem]free device memory failed, runtime errorCode = %d", rtErr);
            }
            rtErr = rtStreamDestroy(rtStream);
            if (rtErr != RT_ERROR_NONE) {
                ACL_LOG_CALL_ERROR("[Destroy][Stream]destory stream failed, runtime errorCode = %d", rtErr);
            }
            return ACL_SUCCESS;
        }

        aclError DvppManager::InitDvppProcessor()
        {
            // init image and video processor
            try {
                switch (version_) {
                    case DVPP_KERNELS_V100: {
                        imageProcessor_ =
                            std::unique_ptr<ImageProcessorV100>(new (std::nothrow)ImageProcessorV100(aclRunMode_));
                        ACL_CHECK_MALLOC_RESULT(imageProcessor_);
                        videoProcessor_ =
                            std::unique_ptr<VideoProcessorV100>(new (std::nothrow)VideoProcessorV100(aclRunMode_));
                        ACL_CHECK_MALLOC_RESULT(videoProcessor_);
                        break;
                    }
                    case DVPP_KERNELS_V200: {
                        imageProcessor_ =
                            std::unique_ptr<ImageProcessorV200>(new (std::nothrow)ImageProcessorV200(aclRunMode_));
                        ACL_CHECK_MALLOC_RESULT(imageProcessor_);
                        videoProcessor_ =
                            std::unique_ptr<VideoProcessorV200>(new (std::nothrow)VideoProcessorV200(aclRunMode_));
                        ACL_CHECK_MALLOC_RESULT(videoProcessor_);
                        break;
                    }
#if (OS_TYPE != WIN)
                    case DVPP_KERNELS_V300: {
                        imageProcessor_ =
                            std::unique_ptr<ImageProcessorV300>(new (std::nothrow)ImageProcessorV300(aclRunMode_));
                        ACL_CHECK_MALLOC_RESULT(imageProcessor_);
                        videoProcessor_ =
                            std::unique_ptr<VideoProcessorV200>(new (std::nothrow)VideoProcessorV200(aclRunMode_));
                        ACL_CHECK_MALLOC_RESULT(videoProcessor_);
                        break;
                    }
#endif
                    default: {
                        ACL_LOG_INNER_ERROR("[Unknown][Kernel]dvpp kernel is unknown version %d.", version_);
                        return ACL_ERROR_FEATURE_UNSUPPORTED;
                    }
                }
            } catch (...) {
                ACL_LOG_INNER_ERROR("[Define][Object]define object image processor with unique_ptr failed.");
                return ACL_ERROR_BAD_ALLOC;
            }

            return ACL_SUCCESS;
        }
    }
}
